// Copyright (c) 2025 Shenzhen Kaihong Digital Industry Development Co., Ltd.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

mod feature;
mod download;
mod patch;
mod build;
mod fixup;
mod deploy;

use std::{collections::{BTreeMap, HashSet}, path::{Path, PathBuf}};

use serde::{Deserialize, Serialize};
use sha2::Sha256;

use crate::{database::local::pkg_config_path, Arch, Pkg, PkgSpec, PkgWithArch, PkgnameWithArch};

use super::{dep_config::DepsConfigError, system::{toolchain::SubToolchainKind, OverlayConfig}, DepsConfig, RequirementsConfig};

pub use feature::*;
pub use download::*;
pub use patch::*;
pub use build::*;
pub use fixup::*;
pub use deploy::*;

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct PkgConfig {
    #[serde(flatten)]
    pub pkg: Pkg,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub tags: Option<Vec<String>>,
    #[serde(default, skip_serializing_if = "PkgConfig::toolchains_is_empty")]
    pub toolchains: HashSet<SubToolchainKind>,
    #[serde(default, skip_serializing_if = "DepsConfig::is_empty")]
    pub depends: DepsConfig,
    #[serde(default, skip_serializing_if = "DepsConfig::is_empty")]
    pub host_depends: DepsConfig,
    #[serde(default, skip_serializing_if = "DepsConfig::is_empty")]
    pub runtime_depends: DepsConfig,
    #[serde(default, skip_serializing_if = "RequirementsConfig::is_empty")]
    pub conflicts: RequirementsConfig,
    #[serde(default, skip_serializing_if = "FeaturesConfig::is_empty")]
    pub features: FeaturesConfig,
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub downloads: Vec<DownloadConfig>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub patch: Option<PatchConfig>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub build: Option<BuildConfig>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub fixup: Option<FixupConfig>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub deploy: Option<DeployConfig>,
    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
    pub overrides: BTreeMap<String, PkgConfigOverrides>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PkgConfigOverrides {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub patch: Option<PatchConfigOverrides>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub build: Option<BuildConfigOverrides>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub deploy: Option<DeployConfigOverrides>,
}

#[derive(Debug, thiserror::Error, PartialEq, Eq)]
pub enum PkgConfigVefifyError {
    #[error(transparent)]
    DepsConfigVerifyError(#[from] DepsConfigError),
    #[error(transparent)]
    FeaturesConfigVerifyError(#[from] FeaturesConfigError),
}

#[derive(Debug, thiserror::Error)]
pub enum PkgConfigLoadError {
    #[error("failed to read pkg config '{1}'")]
    ReadError(#[source] std::io::Error, PathBuf),
    #[error("failed to parse pkg config '{1}'")]
    ParseError(#[source] toml::de::Error, PathBuf),
    #[error("failed to verify pkg config '{1}'")]
    VerifyError(#[source] PkgConfigVefifyError, PathBuf),
}

#[derive(Debug, thiserror::Error, PartialEq, Eq)]
pub enum FeaturesConfigError {
    #[error("default feature '{0}' not found")]
    DefaultFeatureNotFound(String),
    #[error("the depend '{depend}' of feature '{feature}' is not found")]
    FeatureDependNotFound {
        depend: String,
        feature: String,
    },
    #[error("the host depend '{depend}' of feature '{feature}' is not found")]
    FeatureHostDependNotFound {
        depend: String,
        feature: String,
    },
    #[error("the runtime depend '{depend}' of feature '{feature}' is not found")]
    FeatureRuntimeDependNotFound {
        depend: String,
        feature: String,
    },
}

#[macro_export]
macro_rules! deserialize_map_case {
    ($map: expr, $field_var: expr, $field_str: expr) => {{
        if $field_var.is_some() {
            return Err(serde::de::Error::duplicate_field($field_str));
        }
        $field_var = Some($map.next_value()?);
    }};
}

impl PkgConfig {
    pub fn build_options(&self) -> Option<&BuildOptions> {
        self.build.as_ref().map(|build| &build.options)
    }

    fn merge_overrides<'a, I>(&mut self, features: I)
    where I: Iterator<Item = &'a str>,
    {
        Self::merge_overrides_inner(
            &mut self.patch,
            &mut self.build,
            &mut self.deploy,
            &mut self.overrides,
            features,
        );
    }

    fn merge_overrides_with_default(&mut self) {
        Self::merge_overrides_inner(
            &mut self.patch,
            &mut self.build,
            &mut self.deploy,
            &mut self.overrides,
            self.features.default.iter().map(String::as_str),
        );
    }

    fn merge_overrides_inner<'a, I>(
        patch: &mut Option<PatchConfig>,
        build: &mut Option<BuildConfig>,
        deploy: &mut Option<DeployConfig>,
        overrides: &mut BTreeMap<String, PkgConfigOverrides>,
        features: I,
    )   
    where I: Iterator<Item = &'a str>,
    {
        for feature in features {
            let Some(overrides) = overrides.get(feature) else {
                continue;
            };

            if let Some(patch_overrides) = overrides.patch.as_ref() {
                if let Some(patch) = patch.as_mut() {
                    patch.merge_overrides(patch_overrides);
                } else {
                    let mut patch_config = PatchConfig::default();
                    patch_config.merge_overrides(patch_overrides);
                    *patch = Some(patch_config);
                }
            }
            if let Some(build_overrides) = overrides.build.as_ref() {
                if let Some(build) = build.as_mut() {
                    build.merge_overrides(build_overrides);
                }
            }
            if let Some(deploy_overrides) = overrides.deploy.as_ref() {
                if let Some(deploy) = deploy.as_mut() {
                    deploy.merge_overrides(deploy_overrides);
                } else {
                    let mut deploy_config = DeployConfig::default();
                    deploy_config.merge_overrides(deploy_overrides);
                    *deploy = Some(deploy_config);
                }
            }
        }

        overrides.clear();
    }

    fn merge_overlay(&mut self, arch: Arch, overlay: &OverlayConfig) {
        for (dep_pkgname, dep_config) in self.depends.iter_mut() {
            let key = PkgnameWithArch { name: dep_pkgname.clone(), arch };
            if let Some(version_overlay) = overlay.versions.0.get(&key) {
                dep_config.default = version_overlay.clone();
            }
        }
        for (dep_pkgname, dep_config) in self.host_depends.iter_mut() {
            let key = PkgnameWithArch { name: dep_pkgname.clone(), arch: Arch::host() };
            if let Some(version_overlay) = overlay.versions.0.get(&key) {
                dep_config.default = version_overlay.clone();
            }
        }
        for (dep_pkgname, dep_config) in self.runtime_depends.iter_mut() {
            let key = PkgnameWithArch { name: dep_pkgname.clone(), arch };
            if let Some(version_overlay) = overlay.versions.0.get(&key) {
                dep_config.default = version_overlay.clone();
            }
        }

        let key = PkgWithArch { pkg: self.pkg.clone(), arch };
        if let Some(features_overlay) = overlay.features.0.get(&key) {
            self.features.default = features_overlay.clone();
        }
    }

    fn verify(&mut self) -> Result<(), PkgConfigVefifyError> {
        self.depends.verify()?;
        self.host_depends.verify()?;
        self.runtime_depends.verify()?;
        self.verify_features_config()?;
        self.verify_toolchains();
        Ok(())
    }

    fn verify_features_config(&self) -> Result<(), FeaturesConfigError> {
        for feature in self.features.default.iter() {
            if !self.features.configs.contains_key(feature) {
                return Err(FeaturesConfigError::DefaultFeatureNotFound(feature.clone()));
            }
        }
        for (feature, config) in self.features.configs.iter() {
            for depend in config.depends.iter() {
                if !self.depends.contains(depend) {
                    return Err(FeaturesConfigError::FeatureDependNotFound {
                        depend: depend.clone(),
                        feature: feature.clone(),
                    });
                }
            }
            for host_depend in config.host_depends.iter() {
                if !self.host_depends.contains(host_depend) {
                    return Err(FeaturesConfigError::FeatureHostDependNotFound {
                        depend: host_depend.clone(),
                        feature: feature.clone(),
                    });
                }
            }
            for runtime_depend in config.runtime_depends.iter() {
                if !self.runtime_depends.contains(runtime_depend) {
                    return Err(FeaturesConfigError::FeatureRuntimeDependNotFound {
                        depend: runtime_depend.clone(),
                        feature: feature.clone(),
                    });
                }
            }
        }
        Ok(())
    }

    fn verify_toolchains(&mut self) {
        self.toolchains.insert(SubToolchainKind::Basic);
        self.toolchains.insert(SubToolchainKind::Cpp);
    }

    fn toolchains_is_empty(toolchains: &HashSet<SubToolchainKind>) -> bool {
        toolchains.is_empty() ||
            (toolchains.len() == 1 && toolchains.contains(&SubToolchainKind::Basic))
    }

    pub async fn load(spec: &PkgSpec, overlay: Option<&OverlayConfig>) -> Result<Self, PkgConfigLoadError> {
        let path = pkg_config_path(&spec.pkg);
        let content = match tokio::fs::read_to_string(&path).await {
            Ok(s) => s,
            Err(err) => return Err(PkgConfigLoadError::ReadError(err, path)),
        };
        let mut config = match toml::from_str::<PkgConfig>(&content) {
            Ok(config) => config,
            Err(err) => return Err(PkgConfigLoadError::ParseError(err, path)),
        };
        if let Some(overlay) = overlay {
            config.merge_overlay(spec.arch, overlay);
        }
        config
            .verify()
            .map_err(|err| PkgConfigLoadError::VerifyError(err, path))?;
        
        if spec.features.is_empty() {
            config.merge_overrides_with_default();
        } else {
            config.merge_overrides(spec.features.iter().map(|s| s.as_str()));
        }
        Ok(config)
    }

    pub async fn spec_hash<P: AsRef<Path>>(&self, hasher: &mut Sha256, base_dir: P, cross_compile: bool) -> anyhow::Result<()> {
        let base_dir = base_dir.as_ref();
        for download_config in self.downloads.iter() {
            download_config.spec_hash(hasher);
        }
        PatchConfig::spec_hash(self.patch.as_ref(), hasher, base_dir).await?;
        if let Some(build_config) = self.build.as_ref() {
            build_config.spec_hash(hasher, base_dir, cross_compile).await?;
        }
        if let Some(fixup_config) = self.fixup.as_ref() {
            fixup_config.spec_hash(hasher);
        }
        if let Some(deploy_config) = self.deploy.as_ref() {
            deploy_config.spec_hash(hasher, base_dir).await?;
        }
        Ok(())
    }
}

#[cfg(test)]
mod test {
    use std::{collections::{BTreeMap, HashSet}, path::PathBuf};

    use crate::{config::{pkg_config::{BuildConfig, BuildMethod, BuildOptions, CustomBuildConfig, FeatureConfig, FeaturesConfig, FeaturesConfigError, PkgConfig}, DepsConfig, PkgDepConfig, RequirementsConfig}, Pkg, Version, VersionReq};

    #[test]
    fn test_pkg_config_serialize() {
        assert_eq!(
            toml::to_string_pretty(&PkgConfig {
                pkg: Pkg::new("pkg".to_string(), Version::from_standard(1, 2, 3)),
                tags: None,
                toolchains: HashSet::new(),
                depends: DepsConfig::new(),
                host_depends: DepsConfig::new(),
                runtime_depends: DepsConfig::new(),
                conflicts: RequirementsConfig::new(),
                features: FeaturesConfig::default(),
                downloads: Vec::new(),
                patch: None,
                build: None,
                deploy: None,
                fixup: None,
                overrides: BTreeMap::default(),
            }),
            Ok("name = \"pkg\"\nversion = \"1.2.3\"\n".to_string())
        );
        assert_eq!(
            toml::to_string_pretty(&PkgConfig {
                pkg: Pkg::new("pkg".to_string(), Version::from_standard(1, 2, 3)),
                tags: None,
                toolchains: HashSet::new(),
                depends: DepsConfig::new(),
                host_depends: DepsConfig::new(),
                runtime_depends: DepsConfig::from_iter([
                    ("pkg1".to_string(), PkgDepConfig {
                        name: None,
                        default: Version::from_major(1),
                        req: VersionReq::all(),
                        features: vec!["f1".to_string()],
                        optional: true,
                        recursive: None,
                    })
                ]),
                conflicts: RequirementsConfig::new(),
                features: FeaturesConfig {
                    default: vec![],
                    configs: [
                        ("f1".to_string(), FeatureConfig {
                            runtime_depends: vec!["pkg1".to_string()],
                            ..Default::default()
                        })
                    ].into_iter().collect()
                },
                downloads: Vec::new(),
                patch: None,
                build: Some(BuildConfig {
                    method: BuildMethod::Custom(CustomBuildConfig {
                        build_script: PathBuf::from_iter(["/build.sh"].into_iter()),
                        install_script: PathBuf::from_iter(["/install.sh"].into_iter()),
                    }),
                    options: BuildOptions {
                        need_run_compiled: true,
                        ..Default::default()
                    },
                    envs: None,
                }.try_into().unwrap()),
                deploy: None,
                fixup: None,
                overrides: BTreeMap::default(),
            }),
            Ok("\
                name = \"pkg\"\n\
                version = \"1.2.3\"\n\
                \n\
                [runtime-depends.pkg1]\n\
                default = \"1\"\n\
                req = \"*\"\n\
                features = [\"f1\"]\n\
                optional = true\n\
                \n\
                [features.f1]\n\
                runtime-depends = [\"pkg1\"]\n\
                \n\
                [build.options]\n\
                need-run-compiled = true\n\
                \n\
                [build.custom]\n\
                build-script = \"/build.sh\"\n\
                install-script = \"/install.sh\"\n".to_string())
        );
    }

    #[test]
    fn test_pkg_config_deserialize() {
        assert_eq!(
            toml::from_str("name = \"pkg\"\nversion = \"1.2.3\"\n"),
            Ok(PkgConfig {
                pkg: Pkg::new("pkg".to_string(), Version::from_standard(1, 2, 3)),
                tags: None,
                toolchains: HashSet::new(),
                depends: DepsConfig::new(),
                host_depends: DepsConfig::new(),
                runtime_depends: DepsConfig::new(),
                conflicts: RequirementsConfig::new(),
                features: FeaturesConfig::default(),
                downloads: Vec::new(),
                patch: None,
                build: None,
                deploy: None,
                fixup: None,
                overrides: BTreeMap::default(),
            })
        );
        assert_eq!(
            toml::from_str("\
                name = \"pkg\"\n\
                version = \"1.2.3\"\n\
                \n\
                [runtime-depends.pkg1]\n\
                default = \"1\"\n\
                req = \"*\"\n\
                features = [\"f1\"]\n\
                optional = true\n\
                \n\
                [features.f1]\n\
                runtime-depends = [\"pkg1\"]\n\
                \n\
                [build.custom]\n\
                build-script = \"/build.sh\"\n\
                install-script = \"/install.sh\"\n"),
            Ok(PkgConfig {
                pkg: Pkg::new("pkg".to_string(), Version::from_standard(1, 2, 3)),
                tags: None,
                toolchains: HashSet::new(),
                depends: DepsConfig::new(),
                host_depends: DepsConfig::new(),
                runtime_depends: DepsConfig::from_iter([
                    ("pkg1".to_string(), PkgDepConfig {
                        name: None,
                        default: Version::from_major(1),
                        req: VersionReq::all(),
                        features: vec!["f1".to_string()],
                        optional: true,
                        recursive: None,
                    })
                ]),
                conflicts: RequirementsConfig::new(),
                features: FeaturesConfig {
                    default: vec![],
                    configs: [
                        ("f1".to_string(), FeatureConfig {
                            runtime_depends: vec!["pkg1".to_string()],
                            ..Default::default()
                        })
                    ].into_iter().collect()
                },
                downloads: Vec::new(),
                patch: None,
                build: Some(BuildConfig {
                    method: BuildMethod::Custom(CustomBuildConfig {
                        build_script: PathBuf::from_iter(["/build.sh"].into_iter()),
                        install_script: PathBuf::from_iter(["/install.sh"].into_iter()),
                    }),
                    options: BuildOptions::default(),
                    envs: None,
                }.try_into().unwrap()),
                deploy: None,
                fixup: None,
                overrides: BTreeMap::default(),
            })
        );
    }

    #[test]
    fn test_features_config_verify() {
        let config = toml::from_str::<PkgConfig>("\
            name = \"pkg\"\n\
            version = \"1.2.3\"\n\
            \n\
            [runtime-depends.pkg1]\n\
            default = \"1\"\n\
            req = \"*\"\n\
            features = [\"f1\"]\n\
            optional = true\n\
            \n\
            [features]\n\
            default = [\"f2\"]\n\
            \n\
            [features.f1]\n\
            runtime-depends = [\"pkg1\"]\n"
        ).unwrap();
        assert_eq!(
            config.verify_features_config(),
            Err(FeaturesConfigError::DefaultFeatureNotFound("f2".to_string())),
        );

        let config = toml::from_str::<PkgConfig>("\
            name = \"pkg\"\n\
            version = \"1.2.3\"\n\
            \n\
            [runtime-depends.pkg1]\n\
            default = \"1\"\n\
            req = \"*\"\n\
            features = [\"f1\"]\n\
            optional = true\n\
            \n\
            [features]\n\
            default = [\"f1\"]\n\
            \n\
            [features.f1]\n\
            runtime-depends = [\"pkg2\"]\n"
        ).unwrap();
        assert_eq!(
            config.verify_features_config(),
            Err(FeaturesConfigError::FeatureRuntimeDependNotFound {
                depend: "pkg2".to_string(),
                feature: "f1".to_string(),
            })
        );
    }
}